open Ast
open Env

let erreurs_evaluation = ref 0;;
let nombre_variables = ref 1;;

let rec test_return_liste(stl) =
  match stl with
    | [] -> false
    | st::reste -> test_return(st) || test_return_liste(reste)

and test_return(st) =
  match st with
  | NStatementInstruction (instr) ->
    begin
      match instr with
      | NReturn ->
        true
      | NReturnValeur _ -> 
        true
      | _ ->
        false
    end
  | NIf (_,st1,st2) ->
    test_return(st1) && test_return(st2)
  | NWhile (_,st) ->
    test_return(st)
  | NBlock (_,st_liste) ->
    test_return_liste(st_liste)

let rec evalArithmetique (a,affecter) = 
  match a with
  NN (entier) -> print_string "" (* ok *)
  | NVariable (nom,entry) -> 
  begin
    if affecter then (* declarer une nouvelle variable *)
    begin
      let new_entry = ! nombre_variables in
        begin
          incr nombre_variables;
          Env.set_variable (nom,new_entry);
          entry.entry <- new_entry;
        end
    end
  else (* tester la variable *)
  begin
    let entree = Env.get_variable(nom) in (* tester si la variable existe *)
    if (entree > (-1)) then (* oui : lui donner son entry *)
    begin
      entry.entry <- entree
    end
  else
  begin
    (* non : afficher erreur *)
    incr erreurs_evaluation;
    entry.entry <- (-1);
  end
end
end
| NUnop (a) -> begin evalArithmetique(a,false)  end
| NBinOp (a1,op,a2) -> begin evalArithmetique(a1,false) ; evalArithmetique(a2,false)  end
;;

let rec evalArithmetiqueListe (al,affecter) =
  match al with 
  ArL(nArithmetique) -> List.iter (fun x -> evalArithmetique(x,affecter)) nArithmetique
;;

let rec evalAppel appel =
  match appel with
  NAppel (nom,ArL(nArithmetique)) -> 
  if Env.tester_fonction(nom,(Procedure(List.length nArithmetique))) then
    (* decorer les variables *)
    evalArithmetiqueListe(ArL(nArithmetique),false)
  else
    begin
    incr erreurs_evaluation;
    print_endline ("appel à "^nom^" indéfinit\n") (* ok *)
    end
;;

let rec evalInstruction instruction =
  match instruction with
  NReturn -> print_string "" (* ok *)
  | NReturnValeur valeur -> evalArithmetique(valeur,false)
  | NAffectation (variable,valeur) -> begin evalArithmetique(variable,false) ; evalArithmetique(valeur,false) end
  | NAppelMethode (appel) -> evalAppel(appel)
  | NAppelFonction (variable,appel) -> begin evalArithmetique(variable,false) ; evalAppel(appel) end
;;

let rec evalBoolean boolean =
  match boolean with
  NTrue -> print_string "" (* ok *)
  | NFalse -> print_string "" (* ok *)
  | NCmp (a1,comparateur,a2) -> begin evalArithmetique(a1,false) ; evalArithmetique(a2,false) end
  | NotBoolean (boolean) -> evalBoolean(boolean)
  | NOr (booleen1,booleen2) -> begin evalBoolean(booleen1) ; evalBoolean(booleen2) end
  | NAnd (booleen1,booleen2) -> begin evalBoolean(booleen1) ; evalBoolean(booleen2) end
;;

let listevariable = ref [];;

let rec evalVariableListe noeud =
  match noeud with 
  ListeVariable(nArithmetique) ->
  (* retourne la liste des variables *)
  begin
    listevariable := [];
    List.iter (fun v -> 
      match v with
      NVariable (nom,entry) ->
      listevariable := !listevariable@[nom];
      evalArithmetique(v,true);
      | _ ->
      print_string ""
    ) nArithmetique;
    !listevariable
  end
;;

let rec evalDeclaration noeud =
  match noeud with 
  NDeclaration(nom,variableListe) -> 
  evalVariableListe(variableListe)
;;

let listeevaldeclaration = ref [];;

let rec evalDeclarationsListe noeud =
  match noeud with
  DecList(nDeclaration) ->
  begin 
  listeevaldeclaration := [];
  List.iter (fun x -> 
    listeevaldeclaration := !listeevaldeclaration@[evalDeclaration(x)]
  ) nDeclaration;
  !listeevaldeclaration
end
;;



let rec evalStatement noeud =
  match noeud with
  | NStatementInstruction(nInstruction) -> evalInstruction(nInstruction)
  | NIf(nBoolean,nStatement,nStatement2) -> 
  begin
    evalBoolean(nBoolean);
    evalStatement(nStatement);
    evalStatement(nStatement2)
  end
  | NWhile(nBoolean,nStatement) ->
  begin
    evalBoolean(nBoolean);
    evalStatement(nStatement)              
  end    
  | NBlock(declarationsListe,statementsListe) ->
  begin
    (* recupere la liste des variables et les ajoute a l'environnement*)
    let liste_vars = List.flatten(evalDeclarationsListe(declarationsListe)) in 
    begin

      (* test les statements *)
      List.iter (fun x -> evalStatement(x)) statementsListe;

      (* retire les variables de l'environnement *)
      Env.unset_variables(liste_vars)
    end
  end
;;


let rec evalExpression noeud =
  match noeud with 
  | Na(nArithmetique) -> evalArithmetique(nArithmetique,false)
  | Nb(nBoolean) -> evalBoolean(nBoolean)
;;

let rec evalArgument noeud = 
  match noeud with 
  | NArgument (arithmetique,letype) ->
  begin
    evalArithmetique(arithmetique,true);
    match arithmetique with
    NVariable(nom,entry) ->
    nom
    | _ ->
    ""
  end
;;

let listeargumentsdeclaration = ref [];;

let rec evalArgumentsdeclarationListe noeud = 
  match noeud with
  Al(nArgument) -> 
  begin
    listeargumentsdeclaration := [];
    List.iter (fun (x) -> 
      listeargumentsdeclaration := !listeargumentsdeclaration@[evalArgument(x)]
    ) nArgument ;
    !listeargumentsdeclaration  (*renvoie la liste des arguments *)
  end
;;

let rec evalProcedure noeud =
  match noeud with
  NProcedure(nom,adl,nst) -> 
  begin
    (* set de chaque argument*)
    let liste_arguments = evalArgumentsdeclarationListe(adl) in
    let ok = Env.ajouter_fonction(nom,Procedure(List.length(liste_arguments))) in
    begin
      if ok then
      evalStatement(nst)
    else
    begin
      print_endline("Procedure "^nom^" redeclaree\n");
      incr erreurs_evaluation
    end
    ;
    Env.unset_variables(liste_arguments);
    (* unset de chaque argument*)
  end
end
;;

let rec evalFonction noeud =
  match noeud with 
  NFonction(nom,adl,typeRetour,nst) ->  
  begin
    (* set de chaque argument*)
    let liste_arguments = evalArgumentsdeclarationListe(adl) in
    let ok = Env.ajouter_fonction(nom,Fonction(List.length(liste_arguments),typeRetour)) in
    begin
      if ok then
        begin
          evalStatement(nst);
          if test_return(nst) then
            print_string ""
          else
            begin
              print_string ("Il manque un return dans "^nom^"\n");
              incr erreurs_evaluation;
            end
        end
    else
    begin
      print_endline("Fonction "^nom^" redeclaree\n");
      incr erreurs_evaluation
    end
    ;
    Env.unset_variables(liste_arguments)
    (* unset de chaque argument*)
  end
end
;;

let rec evalProgramme noeud =
  match noeud with 
  | NProgrammeProcedure(nProcedure) -> evalProcedure(nProcedure)
  | NProgrammeFonction(nFonction) -> evalFonction(nFonction)
;;

let rec evalNoeudPrincipal noeud = 
  match noeud with
  | Programme(fonctions,main) -> 
  begin
    List.iter (fun x -> evalProgramme(x)) fonctions ;
    evalStatement(main);
    !erreurs_evaluation=0
  end
;;


